/**
*	@author Dr.Nguyen Hua Phung
*	@version 1.0
*	28/6/2006
*	This class is used to generate code at a intermediate level
*
*/
package MP.codegeneration;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.List;

import MP.CompilationException;
import MP.MPC;
import MP.staticchecker.ArrayType;
import MP.staticchecker.BooleanType;
import MP.staticchecker.FunctionType;
import MP.staticchecker.IntegerType;
import MP.staticchecker.ProductType;
import MP.staticchecker.RangeType;
import MP.staticchecker.RealType;
import MP.staticchecker.StringType;
import MP.staticchecker.SymEntry;
import MP.staticchecker.Type;
import MP.staticchecker.VoidType;

public class Emitter {
	
	private Frame frame;
	private final JasminCode jvm = new JasminCode();

	/**
	*	set the active frame which the emitter will generate code for
	*	@param f : the active frame
	*/
	public void setFrame(Frame f) 
	{
		this.frame = f;
	}
	
	/**
	*	get the active frame.
	*	@return the active frame
	*/
	public Frame getFrame() 
	{
		return frame;
	}

	private String emitPUSHICONST(int i) throws CompilationException 
	 {
		frame.push();
		if (i >= -1 && i <= 5){
			return  jvm.emitICONST(i);
			
		} else if (i >= -128 && i <= 127) {
			return  jvm.emitBIPUSH(i);
		}
		else if (i >= -32768 && i <= 32767) {
			return jvm.emitSIPUSH( i );
		} else
			return  jvm.emitLDC(""+ i) ;		
	}

	private  String emitPUSHICONST(String in) throws CompilationException 
	{
		int i = 0;
		if (in.equals("true")) 
			return emitPUSHICONST(1);
		else if (in.equals("false"))
			return emitPUSHICONST(0);
		else {
			try {
				i = Integer.parseInt(in);
			}catch (NumberFormatException e) {
				throw new IllegalOperandException();
			}
			return emitPUSHICONST(i);
		}
	}

	private String emitPUSHFCONST(String in) throws CompilationException 
	 {
		float f = 0;
		try {
			f = Float.parseFloat(in);
		}catch (NumberFormatException e) {
			throw new IllegalOperandException();
		}		
		frame.push();
		DecimalFormat myFormatter = new DecimalFormat("###0.0###");
		String rst = myFormatter.format(f);
		if (rst.equals("0.0") || rst.equals("1.0") ||rst.equals("2.0")) {
			return jvm.emitFCONST(rst) ;
		} else
			return jvm.emitLDC(in);
	}
	/**
	*	generate code to push a constant onto the operand stack.
	*	@param in the lexeme of the constant
	*	@param type the type of the constant
	*/
	public  String emitPUSHCONST(String in,Type type) throws CompilationException 
	{
		
		if ((type == Type.INTTYPE )||(type == Type.BOOLTYPE ))
			return emitPUSHICONST(in);
		else if (type == Type.REALTYPE)
			return emitPUSHFCONST(in);
		else if (type instanceof StringType) {
			frame.push();
			return jvm.emitLDC("\""+in+"\"");
		}
		else 
			throw new IllegalOperandException();
	}
	/**
	*	generate code to put an block-scoped array variable onto the operand stack.
	*	@param sym the symbol entry of the variable
	*/
	public String emitREADARRAYELE(SymEntry sym) throws CompilationException 
	{
		frame.push();
		int index = (Integer)sym.getObject();
		if ( sym.getType() instanceof ArrayType )
			return jvm.emitALOAD(index);
		else
			throw new IllegalOperandException();
	}
	/** 	generate the var directive for a local variable.
	*	@param in the index of the local variable.
	*	@param varName the name of the local variable.
	*	@param inType the type of the local variable.
	*	@param fromLabel the starting label of the scope where the variable is active.
	*	@param toLabel the ending label  of the scope where the variable is active.
	*/
	public String emitVAR(int in,String varName, Type inType, int fromLabel, int toLabel) {
		return jvm.emitVAR(in,varName,inType.getJVMType(),fromLabel,toLabel);
	}

	/**
	*	generate code to put the value of a variable onto the operand stack.
	*	@param sym the symbol entry of the variable.

	*/
	public String emitREADVAR(SymEntry sym) throws CompilationException 
	{
		int scope = sym.getScope();
		frame.push();
		if (scope == SymEntry.BLOCK) {
			int index = (Integer)sym.getObject();
			if (sym.getType() == Type.INTTYPE || sym.getType() == Type.BOOLTYPE) 
				return jvm.emitILOAD(index);
			else  if ( sym.getType() == Type.REALTYPE )
				return jvm.emitFLOAD(index);
			else 
				throw new IllegalOperandException();
		}
		else
			//TODO: more for other scope
			return "";
	}

	/**
	*	generate code to pop a value on top of the operand stack and store it to a block-scoped variable.
	*	@param sym the symbol entry of the variable.
	*/
	public String emitWRITEVAR(SymEntry sym) throws CompilationException 
	{
		frame.pop();
		int index = (Integer)sym.getObject();
		if (sym.getType() == Type.INTTYPE || sym.getType() == Type.BOOLTYPE) 
			return jvm.emitISTORE(index);
		else if (sym.getType() == Type.REALTYPE )
			return jvm.emitFSTORE(index);
		else 
			throw new IllegalOperandException();
	}	
	/** 	generate the field (static) directive for a global variable or constant.
	*	@param lexeme the name of the global variable.
	*	@param in the type of the global variable.
	*	@param isFinal true in case of constant; false otherwise
	*/
	public String emitSTATICFIELD(String lexeme, Type in, boolean isFinal) {
		return jvm.emitSTATICFIELD(lexeme,in.getJVMType(),isFinal);
	}

	/**	generate code to invoke a static method
	*	@param lexeme the qualified name of the method(i.e., class-name/method-name)
	*	@param in the type descriptor of the method.
	*/
	public String emitINVOKESTATIC(String lexeme,Type in) throws CompilationException
	{
		FunctionType pt = (FunctionType) in;
		Type pType = pt.getParameterType();
		//if (pType != null && (!(pType == Type.VOIDTYPE))) {
		while (pType != null) {
			frame.pop();
			pType = ((ProductType)pType).getE2Type();
		}
		Type rType = ((FunctionType)in).getReturnType();
		if (rType != null && (!(rType == Type.VOIDTYPE)))
			frame.push();		
		return jvm.emitINVOKESTATIC(lexeme,in.getJVMType());
	}
	/**
	*	generate iadd, isub, fadd or fsub.
	*	@param lexeme the lexeme of the operator.
	*	@param in the type of the operands.
	*/	
	public String emitADDOP(String lexeme,Type in)throws CompilationException 
	{
		frame.pop();
		if (lexeme.equals("+")) {
			if (in == Type.INTTYPE)
				return jvm.emitIADD();
			else 
				return jvm.emitFADD();
		} else 
			if (in == Type.INTTYPE)
				return jvm.emitISUB();
			else 
				return jvm.emitFSUB();
	}
	/**
	*	generate imul, idiv, fmul or fdiv.
	*	@param lexeme the lexeme of the operator.
	*	@param in the type of the operands.
	*/	
	
	public String emitMULOP(String lexeme,Type in) throws CompilationException 
	{
		frame.pop();
		if (lexeme.equals("*")) {
			if (in == Type.INTTYPE)
				return jvm.emitIMUL();
			else 
				return jvm.emitFMUL();
		} 
		else if (in == Type.INTTYPE)
			return jvm.emitIDIV();
		else 
			return jvm.emitFDIV();
	}
	/**
	*	generate idiv
	*	@param lexeme the lexeme of the operator.
	*	@param in the type of the operands.
	*/	
	
	public String emitDIV() throws CompilationException 
	{
		frame.pop();
		return jvm.emitIDIV();
	}
	/**
	*	generate irem
	*	@param lexeme the lexeme of the operator.
	*	@param in the type of the operands.
	*/	
	
	public String emitMOD() throws CompilationException 
	{
		frame.pop();
		return jvm.emitIDIV();
	}
	/**
	*	generate iand.
	*/	

	public String emitANDOP() throws CompilationException 
	{
		frame.pop();
		return jvm.emitIAND();
	}	
	/**
	*	generate ior.
	*/	
	public String emitOROP() throws CompilationException 
	{
		frame.pop();
		return jvm.emitIOR();
	}

	/** 	generate the method directive for a function.
	*	@param lexeme the qualified name of the method(i.e., class-name/method-name).
	*	@param in the type descriptor of the method.
	*	@param isStatic <code>true</code> if the method is static; <code>false</code> otherwise.
	*/
	public String emitMETHOD(String lexeme, Type in, boolean isStatic) throws CompilationException {
		return jvm.emitMETHOD(lexeme,in.getJVMType(),isStatic);
	}
	/** 	generate the end directive for a function.
	*/
	public String emitENDMETHOD() {
		StringBuffer buff = new StringBuffer();
		buff.append(jvm.emitLIMITSTACK(frame.getMaxOpStackSize()));
		buff.append(jvm.emitLIMITLOCAL(frame.getMaxIndex()));
		buff.append(jvm.emitENDMETHOD());
		return buff.toString();
	}

	/**	generate code to create an array
	*	@param in the type of the elements of the array
	*/
	public String emitNEWARRAY(Type in) throws CompilationException {
		return jvm.emitNEWARRAY(in.toString());
	}
	/** 	generate code for a class init method.
	*/
	public String emitCLINIT() throws CompilationException {
		StringBuffer result = new StringBuffer();
		Type tmp = new FunctionType(null,new VoidType());
		Frame tmpFrame = new Frame();
		tmpFrame.enterScope();
		result.append(emitMETHOD("<clinit>",tmp,true));
		result.append(jvm.emitLIMITSTACK(1));
		result.append(jvm.emitLIMITLOCAL(0));
		result.append(emitRETURN(null));
		result.append(jvm.emitENDMETHOD());
		tmpFrame.exitScope();
		return result.toString();
	}
	
	/** 	generate code to initialize a local array variable.<p>
	*	@param index the index of the local variable.
	*	@param in the type of the local array variable.
	*/
	public String emitINITARRAY(int index,Type in) throws CompilationException  {
		StringBuffer buff = new StringBuffer();
		ArrayType at = (ArrayType) in;
		buff.append(emitPUSHICONST(((RangeType)at.getIType()).getUpper() - ((RangeType)at.getIType()).getLower()));
		buff.append(emitNEWARRAY(at.getEType()));
		frame.pop();
		buff.append(jvm.emitASTORE(index));
		return buff.toString();
	}
	/** 	generate code to initialize local array variables.
	*	@param in the list of symbol entries corresponding to local array variable.
	*/
	
	public String emitLISTARRAY(List<SymEntry> in) throws CompilationException {
		StringBuffer result = new StringBuffer();
		for (Iterator<SymEntry> it = in.iterator();it.hasNext();) {
			SymEntry sym = it.next();
			ArrayType at =(ArrayType)sym.getType();
			result.append(emitINITARRAY((Integer)sym.getObject(),at));
		}
		in.clear();
		return result.toString();
	}
	/**
	*	generate code to jump to label if the value on top of operand stack is true.<p>
	*	ifgt label
	*	@param label the label where the execution continues if the value on top of stack is true.
	*/
	public String emitIFTRUE(int label) throws CompilationException 
	{
		frame.pop();
		return jvm.emitIFGT(label);
	}
	/**
	*	generate code to jump to label if the value on top of operand stack is false.<p>
	*	ifle label
	*	@param label the label where the execution continues if the value on top of stack is false.
	*/
	public String emitIFFALSE(int label) throws CompilationException 
	{
		frame.pop();
		return jvm.emitIFLE(label);
	}
	/** 	generate code to duplicate the value on the top of the operand stack.<p>
	*	Stack:<p>
	*	Before: ...,value1<p>
	*	After:  ...,value1,value1<p>
	*/
	public String emitDUP() throws CompilationException 
	{
		frame.push();
		return jvm.emitDUP();
	}
	/**	generate code to pop the value on the top of the operand stack.
	*/
	public String emitPOP() throws CompilationException 
	{
		frame.pop();
		return jvm.emitPOP();
	}
	/** 	generate code to exchange an integer on top of stack to a floating-point number.
	*/
	public String emitI2F() throws CompilationException 
	{
		return jvm.emitI2F();
	}
	/**	generate code to return.
	*	<ul>
	*	<li>ireturn if the type is IntegerType or BooleanType
	*	<li>freturn if the type is RealType
	*	<li>return if the type is null
	*	</ul>
	*	@param in the type of the returned expression.
	*/

	public String emitRETURN(Type in) throws CompilationException 
	{
		if (in == null){
			return jvm.emitRETURN();
		} else {
			frame.pop();
			if (in == Type.INTTYPE || in == Type.BOOLTYPE)
				return jvm.emitIRETURN();
			else
				return jvm.emitFRETURN();
		}
	}
/** generate code that represents a label	
	 *	@param label the label
	 *	@return code Label<label>:
	 */
	public String emitLABEL(int label) throws CompilationException {
		return jvm.emitLABEL(label);
	}
	/** generate code to jump to a label	
	 *	@param label the label
	 *	@return code goto Label<label>
	 */
	public String emitGOTO(int label) throws CompilationException {
		return jvm.emitGOTO(label);
	}
	/**	generate some starting directives for a class.<p>
	*	.source MPC.CLASSNAME.java<p>
	*	.class public MPC.CLASSNAME<p>
	*	.super java/lang/Object<p>
	*/	
	public String emitPROLOG() throws CompilationException {
		StringBuffer result = new StringBuffer();
		result.append(jvm.emitSOURCE(MPC.CLASSNAME+".java"));
		result.append(jvm.emitCLASS("public "+MPC.CLASSNAME));
		result.append(jvm.emitSUPER("java/lang/Object"));
		return result.toString();
	}
	/** print out the code to screen
	*	@param in the code to be printed out
	*/
	public void  printout(String in) {
		System.out.print(in);
	}	
}
		
